--[[
Title: asn1 encoder/decoder
Author(s): LiXizhi
Desc: 
References
- https://github.com/Mashape/kong/blob/master/kong/plugins/ldap-auth/asn1.lua
- https://svn.nmap.org/nmap/nselib/asn1.lua
Use Lib:
-------------------------------------------------------
NPL.load("(gl)script/ide/System/Encoding/asn1.lua");
local Encoding = commonlib.gettable("System.Encoding");
local decoder = Encoding.asn1.ASN1Decoder:new()
local pos, data = decoder:decode(rawdata, 1);
-------------------------------------------------------
]]
NPL.load("(gl)script/ide/math/bit.lua");
NPL.load("(gl)script/ide/System/Encoding/bin_pack.lua");
local bit = mathlib.bit;
local Encoding = commonlib.gettable("System.Encoding");
local bpack = string.pack
local bunpack = string.unpack
local math = math
local setmetatable = setmetatable
local table = table
local string_reverse = string.reverse
local string_char = string.char

local asn1 = commonlib.gettable("System.Encoding.asn1");

asn1.BERCLASS = {
Universal = 0,
Application = 64,
ContextSpecific = 128,
Private = 192
}

------------------------------
-- decoder
------------------------------
asn1.ASN1Decoder = {

new = function(self, o)
	o = o or {}
	setmetatable(o, self)
	self.__index = self
	o:registerBaseDecoders()
	return o
end,

-- return pos, data
decode = function(self, encStr, pos)
	local etype, elen
	local newpos = pos
	newpos, etype = bunpack(encStr, "X1", newpos)
	newpos, elen = self.decodeLength(encStr, newpos)
	if self.decoder[etype] then
		return self.decoder[etype](self, encStr, elen, newpos)
	--elseif(type(elen) == "number") then
	--return newpos + elen - 1, nil
	else
		return newpos, nil
	end
end,

setStopOnError = function(self, val)
	self.stoponerror = val
end,

registerBaseDecoders = function(self)
	self.decoder = {}
	
	self.decoder["0A"] = function(self, encStr, elen, pos)
		return self.decodeInt(encStr, elen, pos)
	end
	
	self.decoder["8A"] = function(self, encStr, elen, pos)
		return bunpack(encStr, "A" .. elen, pos)
	end
	
	self.decoder["31"] = function(self, encStr, elen, pos)
		return pos, nil
	end
	
	-- reserved for BER
	--self.decoder["00"] = function(self, encStr, elen, pos)
	--return self:decode(encStr, pos);
	--end
	
	-- Boolean
	self.decoder["01"] = function(self, encStr, elen, pos)
		local val = bunpack(encStr, "X", pos)
		if val ~= "FF" then
			return pos, true
		else
			return pos, false
		end
	end
	
	-- Integer
	self.decoder["02"] = function(self, encStr, elen, pos)
		return self.decodeInt(encStr, elen, pos)
	end
	
	-- Bit String
	self.decoder["03"] = function(self, encStr, elen, pos)
		return bunpack(encStr, "A" .. elen, pos)
	end
	
	-- Octet String
	self.decoder["04"] = function(self, encStr, elen, pos)
		return bunpack(encStr, "A" .. elen, pos)
	end
	
	-- Null
	self.decoder["05"] = function(self, encStr, elen, pos)
		return pos, false
	end
	
	-- Object Identifier
	self.decoder["06"] = function(self, encStr, elen, pos)
		return self:decodeOID(encStr, elen, pos)
	end
	
	-- Context specific tags
	self.decoder["30"] = function(self, encStr, elen, pos)
		return self:decodeSeq(encStr, elen, pos)
	end
end,

registerTagDecoders = function(self, tagDecoders)
	self:registerBaseDecoders()
	for k, v in pairs(tagDecoders) do
		self.decoder[k] = v
	end
end,

decodeLength = function(encStr, pos)
	local elen
	pos, elen = bunpack(encStr, 'C', pos)
	if(elen > 128) then
		elen = elen - 128
		local elenCalc = 0
		local elenNext
		for i = 1, elen do
			elenCalc = elenCalc * 256
			pos, elenNext = bunpack(encStr, 'C', pos)
			elenCalc = elenCalc + elenNext
		end
		elen = elenCalc
	end
	return pos, elen
end,

decodeSeq = function(self, encStr, len, pos)
	local seq = {}
	local sPos = 1
	local sStr
	pos, sStr = bunpack(encStr, "A" .. len, pos)
	while(sPos < len) do
		local newSeq
		sPos, newSeq = self:decode(sStr, sPos)
		if(not(newSeq) and self.stoponerror) then break end
		table.insert(seq, newSeq)
	end
	return pos, seq
end,

decode_oid_component = function(encStr, pos)
	local octet
	local n = 0
	
	repeat
		pos, octet = bunpack(encStr, "C", pos)
		n = n * 128 + bit.band(0x7F, octet)
	until octet < 128
	
	return pos, n
end,

decodeOID = function(self, encStr, len, pos)
	local last
	local oid = {}
	local octet
	
	last = pos + len - 1
	if pos <= last then
		oid._snmp = '06'
		pos, octet = bunpack(encStr, "C", pos)
		oid[2] = math.fmod(octet, 40)
		octet = octet - oid[2]
		oid[1] = octet / 40
	end
	
	while pos <= last do
		local c
		pos, c = self.decode_oid_component(encStr, pos)
		oid[#oid + 1] = c
	end
	
	return pos, oid
end,

decodeInt = function(encStr, len, pos)
	if(len <= 8) then
		local hexStr;
		pos, hexStr = bunpack(encStr, "X" .. len, pos)
		local value = tonumber(hexStr, 16)
		if(value >= math.pow(256, len) / 2) then
			value = value - math.pow(256, len)
		end
		return pos, value
	else
		local binStr;
		pos, binStr = bunpack(encStr, "A" .. len, pos)
		return pos, binStr;
	end
end
}

------------------------------
-- encoder
------------------------------
asn1.ASN1Encoder = {

new = function(self)
	local o = {}
	setmetatable(o, self)
	self.__index = self
	o:registerBaseEncoders()
	return o
end,

encodeSeq = function(self, seqData)
	return bpack('XAA' , '30', self.encodeLength(#seqData), seqData)
end,

encode = function(self, val)
	local vtype = type(val)
	
	if self.encoder[vtype] then
		return self.encoder[vtype](self, val)
	end
end,

registerTagEncoders = function(self, tagEncoders)
	self:registerBaseEncoders()
	for k, v in pairs(tagEncoders) do
		self.encoder[k] = v
	end
end,

registerBaseEncoders = function(self)
	self.encoder = {}
	
	self.encoder['table'] = function(self, val)
		if(val._ldap == '0A') then
			local ival = self.encodeInt(val[1])
			local len = self.encodeLength(#ival)
			return bpack('XAA', '0A', len, ival)
		end
		if(val._ldaptype) then
			local len
			if val[1] == nil or #val[1] == 0 then
				return bpack('XC', val._ldaptype, 0)
			else
				len = self.encodeLength(#val[1])
				return bpack('XAA', val._ldaptype, len, val[1])
			end
		end
		
		local encVal = ""
		for _, v in ipairs(val) do
			encVal = encVal .. self.encode(v) -- todo: buffer?
		end
		local tableType = "\x30"
		if(val["_snmp"]) then
			tableType = bpack("X", val["_snmp"])
		end
		return bpack('AAA', tableType, self.encodeLength(#encVal), encVal)
	end
	
	-- Boolean encoder
	self.encoder['boolean'] = function(self, val)
		if val then
			return bpack('X', '01 01 FF')
		else
			return bpack('X', '01 01 00')
		end
	end
	
	-- Integer encoder
	self.encoder['number'] = function(self, val)
		local ival = self.encodeInt(val)
		local len = self.encodeLength(#ival)
		return bpack('XAA', '02', len, ival)
	end
	
	-- Octet String encoder
	self.encoder['string'] = function(self, val)
		local len = self.encodeLength(#val)
		return bpack('XAA', '04', len, val)
	end
	
	-- Null encoder
	self.encoder['nil'] = function(self, val)
		return bpack('X', '05 00')
	end

end,

encode_oid_component = function(n)
	local parts = {}
	parts[1] = string_char(bit.mod(n, 128))
	while n >= 128 do
		n = bit.rshift(n, 7)
		parts[#parts + 1] = string_char(bit.mod(n, 128) + 0x80)
	end
	return string_reverse(table.concat(parts))
end,

encodeInt = function(val)
	local lsb = 0
	if val > 0 then
		local valStr = ""
		while(val > 0) do
			lsb = math.fmod(val, 256)
			valStr = valStr .. bpack('C', lsb)
			val = math.floor(val / 256)
		end
		if lsb > 127 then
			valStr = valStr .. "\0"
		end
		
		return string_reverse(valStr)
	elseif val < 0 then
		local i = 1
		local tcval = val + 256
		while tcval <= 127 do
			tcval = tcval + (math.pow(256, i) * 255)
			i = i + 1
		end
		local valStr = ""
		while(tcval > 0) do
			lsb = math.fmod(tcval, 256)
			valStr = valStr .. bpack("C", lsb)
			tcval = math.floor(tcval / 256)
		end
		return string_reverse(valStr)
	else -- val == 0
		return bpack("x")
	end
end,

encodeLength = function(len)
	if len < 128 then
		return string_char(len)
	else
		local parts = {}
		
		while len > 0 do
			parts[#parts + 1] = string_char(bit.mod(len, 256))
			len = bit.rshift(len, 8)
		end
		
		return string_char(#parts + 0x80) .. string_reverse(table.concat(parts))
	end
end
}

function asn1.BERtoInt(class, constructed, number)
	local asn1_type = class + number
	
	if constructed == true then
		asn1_type = asn1_type + 32
	end
	
	return asn1_type
end

function asn1.intToBER(i)
	local ber = {}
	if bit.band(i, asn1.BERCLASS.Application) == asn1.BERCLASS.Application then
		ber.class = asn1.BERCLASS.Application
	elseif bit.band(i, asn1.BERCLASS.ContextSpecific) == asn1.BERCLASS.ContextSpecific then
		ber.class = asn1.BERCLASS.ContextSpecific
	elseif bit.band(i, asn1.BERCLASS.Private) == asn1.BERCLASS.Private then
		ber.class = asn1.BERCLASS.Private
	else
		ber.class = asn1.BERCLASS.Universal
	end
	if bit.band(i, 32) == 32 then
		ber.constructed = true
		ber.number = i - ber.class - 32
	else
		ber.primitive = true
		ber.number = i - ber.class
	end
	return ber
end
